﻿namespace ClearLines.Bumblebee

open System

type Solution<'s> = { Definition: 's; Quality: float }

// Scout bees carry a Solution.
// Active bees carry a Solution, 
// and the current number of trips made
// in the same solution area.
type Bee<'s> = 
   | Scout of Solution<'s> 
   | Active of Solution<'s> * int

type Configuration = 
   { 
      ActiveBees: int;
      ScoutBees: int;
      InactiveBees: int;
      ProbaMistake: float;
      ProbaConvince: float;
      Trips: int
   }

module internal Hive =

   open System

   let defaultActiveBees = 6
   let defaultScoutBees = 2
   let defaultInactiveBees = 100
   let defaultProbaMistake = 0.01
   let defaultProbaConvince = 0.9
   let defaultTrips = 20

   // Given a solution and an evaluation function,
   // produces a solution with its quality.
   let evaluate qualityOf solution =
      { Definition = solution; Quality = qualityOf solution }

   // Retrieve the solution carried by a bee.
   let solutionOf bee =
      match bee with
      | Scout(solution) -> solution
      | Active(solution, trips) -> solution
   
   /// <summary>Update original with candidate</summary>
   let updateWith candidate convince original =
      match candidate.Quality > original.Quality 
            && convince() with
      | true -> candidate 
      | false -> original

   let waggleUpdate (rng: Random) (config: Configuration) inactives bee =
      let convince() = rng.NextDouble() < config.ProbaConvince
      let candidate = solutionOf(bee)
      Array.map (fun original -> 
         updateWith candidate convince original) inactives

   let searchResult mistake solution neighbor =
      let isBetter = neighbor.Quality > solution.Quality
      let mistaken = mistake()
      match isBetter, mistaken with
      | true, false -> neighbor
      | true, true -> solution
      | false, false -> solution
      | false, true -> neighbor
            
   let redirect replacement maxTrips (bee: Bee<'s>) =
      match bee with
      | Scout(_) -> bee
      | Active(solution, trip) -> 
         match trip > maxTrips with
         | true -> Active(replacement(), 0)
         | false -> Active(solution, trip + 1)

   let initialize config generator = seq {
      for i in 1 .. config.ActiveBees -> Active(generator (), 0)
      for i in 1 .. config.ScoutBees -> Scout(generator()) } 

   let initialState config generator =
      let inactives = [| for i in 1 .. config.InactiveBees -> generator () |]
      let best = Array.maxBy (fun s -> s.Quality) inactives
      (best, inactives)

   /// <summary>Updates a Bee with the result of its search.
   /// A scout bee will return a solution from the Generator,
   /// whereas an Active be will return a solution in the neighborhood
   /// of its initial solution.</summary>
   let search (rng: Random) (problem: Problem<'s>) (config: Configuration) (bee: Bee<'s>) =
      let evaluator = evaluate problem.Evaluate
      match bee with
      | Scout(_) -> 
         let found = problem.Generate(rng) |> evaluator
         Scout(found)
      | Active(solution, trip) -> 
         let neighbor = evaluator(problem.Mutate rng solution.Definition)
         let mistakes() = rng.NextDouble() < config.ProbaMistake
         let found = searchResult mistakes solution neighbor
         Active(found, trip)